package parserjc.jaccie;

import java.util.*;

/**
 * This class is scanner, which allows to scan an scanner input.
 * The components of this scanner are automatas. The first added
 * automata get the highest priority.
 * The scanner provides some scanner services:
 * The current line number and column number of the scanner input.
 * And the current recocnized String. You are able to control the
 * scanning process with the StartEndAction methods.
 *
 * @author Christoph Reich
 * @version 1.00 09.1997
 */
public class ScannerScanner implements StartEndAction,
                                ScanService {

String ls = "\n";//System.getProperty("line.separator");

/**
 * The name of the scanner.
 */
private String scannerName;

/**
 * The current best automata. This automata could become
 * the accumulated token.
 */
private String bestAutomat = null;

/**
 * The current recognized string.
 */
private String recognizeString = "";

/**
 * The automatas of the scanner.
 */
private Vector automataList      = new Vector();

/**
 * The active automatas of the scanner.
 */
private Vector activeAutomata    = new Vector();

/**
 * The passive automatas of the scanner.
 */
private Vector passiveAutomata   = new Vector();

/**
 * The complete list of accumulated token.
 */
private Vector accumulatedTokens = new Vector();

/**
 * The scanner input.
 */
private ScannerInput scannerInput;

/**
 * The StartEndAction interface. It controls the scanning process.
 */
private StartEndAction startEndAction = this;

/**
 * The input line where an error occures while scanning.
 */
private int errorLine = 0;

/**
 * The input column where an error occures while scanning.
 */
private int errorColumn = 0;

/**
 * The java code of the StartEndAction class for building
 * the scanner. Needed for the toString method.
 */
private StringBuffer javaCode = null;

/**
 * Indicates that the current input-symbol is the first of a recognizing token.
 */
private boolean firstSymbol = true;

/**
 * The scanner service interface. A class which implements the
 * StartEndAction interface can set this ScanService and accecs to
 * provides methods (current line-,column- number, recognized string.
 */
protected ScanService scannerService;

    /**
     * Constructs a scanner.
     * @param scannerName the name of the scanner
     * @param input the scanner input
     * @see ScannerInput
     */
    public ScannerScanner(String scannerName, ScannerInput input) {
        this.scannerName = scannerName;
        this.scannerInput = input;
    }

    // StartEndAction implemented methods

    /**
     * This method will be called before a automata try
     * to do a transition with a symbol.
     * @param automatName the start block should be a case
     *        for the automata name, to compute his start condition.
     */
    public boolean startBlocks(String automatName) {
        return true;
    }

    /**
     * This method will called before recognizing a token.
     * @param automatName the end block should be a case
     *        for the automata name, to compute his end condition.
     */
    public boolean endBlocks(String automatName) {
        return true;
    }

    /**
     * This method will called after recocognizing a token.
     * @param automatName the action block should be a case
     *        for the automata name, to do his action.
     */
    public void actionBlocks(String automatName) {
    }

    /**
     * Sets the scanner service interface. This provides the subclasses
     * to access to special methods (lineNumber, columnNumber, recognized String)
     * @param scanService the scanner service interface
     */
    public void setScannerService(ScanService scanService) {
        this.scannerService = scanService;
    }

    // End StartEndAction methods

    /**
     * Sets the StartEndAction for controling the scanning process.
     * @param startEndAction the StartEndAction interface
     */
    public void setStartEndAction(StartEndAction startEndAction) {
        this.startEndAction = startEndAction;
        startEndAction.setScannerService(this);
    }

    /**
     * Sets the StartEndAction for controling the scanning process.
     * @param startEndAction the StartEndAction interface
     * @param javaCode the code for the toString method
     * @see buildScanner
     */
    public void setStartEndAction(StartEndAction startEndAction,StringBuffer javaCode) {
        this.startEndAction = startEndAction;
        this.javaCode = javaCode;
        startEndAction.setScannerService(this);
    }

    /**
     * Needed for recognize the last token and reset the scanner.
     */
    private void endOfInputHandling() {
        if (!bestAutomat.equals(""))
            scannerInput.setLastTokenPos(0);
        handleToken();
        setActive();
        recognizeString = "";
        bestAutomat = "";
    }

    /**
     * @return a string representation of th automata.
     * Used for the interactive testing
     * @param automata the activ or passive automata
     */
    private String getAutomata(Vector automata) {
        String string = new String();
        for (int i=0; i<automata.size(); i++) {
            Integer automatInteger = (Integer)automata.elementAt(i);
            int index = automatInteger.intValue();
            Automat currentAutomat = (Automat)automataList.elementAt(index);
            String automatName = currentAutomat.getAutomatName();
            String stateText = currentAutomat.getStateText();
            if (automatName.startsWith("JACCIE"))
                string += automatName+""+ls;
            else string += automatName+" "+stateText+""+ls;
        }
        return string;
    }

    /**
     * Handles the accumulated token.
     * At first the best automata will asked if his end condition is true.
     * If it is true we are allowed to recocnizes a token. If the best
     * automata not empty (null) we found a token. The java action block
     * will called.
     */
    private void handleToken() {
        boolean recognizeToken = startEndAction.endBlocks(bestAutomat);
        if (recognizeToken) {
          if (bestAutomat.equals("") ) {}
          else if (bestAutomat.equals("JACCIE_Separators"))
               accumulatedTokens.addElement(bestAutomat);
          else if (bestAutomat.equals("JACCIE_Comments")) {
               scannerInput.skipUntil(scannerInput.getComment(recognizeString));
               accumulatedTokens.addElement(bestAutomat);
          }
          else if (bestAutomat.equals("JACCIE_Strings")) {
               recognizeString = scannerInput.getString(recognizeString);
               accumulatedTokens.addElement("string \"" +recognizeString+"\"");
          }
          else if (bestAutomat.equals("JACCIE_Verbatims")) {
               recognizeString = scannerInput.getVerbatim(recognizeString);
               //die G�nsef��chen sollten weg
               accumulatedTokens.addElement(recognizeString);
               //accumulatedTokens.addElement(recognizeString +" \"\"");
          }
          else accumulatedTokens.addElement(bestAutomat +
                        " \"" +recognizeString+"\"");
          startEndAction.actionBlocks(bestAutomat);
        }
    }

    /**
     * The main scan algorithmus. It changes the character in a hashcode.
     * Test the symbol, if it is a valid symbol and returns 0 in this case.
     * If this symbol not valid, the input will be set back for one char.
     * If we found a best automata (automata with the highest priority and last
     * guilty) its return 1 and all automatas will be set active. Else an error
     * is in the input and its return -1.
     * @param character the testing character
     */
    private int scan(Character character) {
        Integer symbol = new Integer (character.hashCode());
        boolean isValidSymbol = testSymbol(symbol);
        if (!isValidSymbol) {
            errorLine = scannerInput.getLineNumber();
            errorColumn = scannerInput.getColumnNumber();
            scannerInput.setIndex();
            if (!bestAutomat.equals("")) {
                handleToken();
                scannerInput.setLastTokenPos(0);
                setActive();
                recognizeString = "";
                bestAutomat = "";
                firstSymbol = true;
                return 1; // found token
            }
            return -1; // error
        }
        else {
            recognizeString= recognizeString + character.toString();
            return 0; // good symbol
        }
    }

    /**
     * Fills the active list with all automatas and
     * askes the automatas to reset themself.
     */
    private void setActive() {
        activeAutomata.removeAllElements();
        passiveAutomata.removeAllElements();
        for (int i=0; i<automataList.size(); i++) {
            ((Automat)automataList.elementAt(i)).reset();
            activeAutomata.addElement(new Integer(i));
        }
    }

    /**
     * Tests the current symbol and returns true if the symbol (character/hashcode)
     * is valid (means minimum one automata knows a transition with this symbol).
     * The algorithmus:
     * Every active automata will be asked if his start condition is true else the
     * automata will set passive.
     * Then the active automata has to do a transition with the current symbol if he
     * is able to do this. If he is not able, he will become passive.
     * If we have not still found a best automat and the active automat is in a final
     * state, we found the best automata with the highest priority (until jet) because
     * the automatas are ordered.
     * @param symbol the testing symbol
     */
    private boolean testSymbol(Integer symbol) {
        boolean foundValidAutomat = false;
        Enumeration activeList = activeAutomata.elements();
        while (activeList.hasMoreElements()) {
            Integer automatInteger = (Integer)activeList.nextElement();
            int index = automatInteger.intValue();
            Automat currentAutomat = (Automat)automataList.elementAt(index);
            String automatName = currentAutomat.getAutomatName();
            boolean stayActive = true;
            if (firstSymbol)
                  stayActive = startEndAction.startBlocks(automatName);
            if (!stayActive) passiveAutomata.addElement(automatInteger);
            else {
                boolean isElement = currentAutomat.testSymbol(symbol);
                if (!isElement) passiveAutomata.addElement(automatInteger);
                if (!foundValidAutomat && currentAutomat.isFinalState()) {
                    bestAutomat = currentAutomat.getAutomatName();
                    scannerInput.setValidChar();
                    foundValidAutomat = true;
                }
            }
        }
        Enumeration passiveList = passiveAutomata.elements();
        while (passiveList.hasMoreElements()) {
            Integer automatInteger = (Integer)passiveList.nextElement();
            if (activeAutomata.contains(automatInteger))
                activeAutomata.removeElement(automatInteger);
        }
        firstSymbol = false;
        if (activeAutomata.isEmpty()) return false;
        return true;
    }

    /**
     * Adds an automat to the scanner
     * @see Automat
     */
    public void add(Automat automat) {
        automataList.addElement(automat);
        setActive();
    }

    /**
     * @return the string representation of the active automata.
     */
    public String getActiveAutomata() {
        return getAutomata(activeAutomata);
    }

    /**
     * @return the name of the best automata
     */
    public String getBestAutomat() {
      return bestAutomat;
    }

    // ScanServise implemented methods

    /**
     * @return the current column number of the input string
     */
    public int getColumnNumber() {
        return scannerInput.getColumnNumber();
    }

    /**
     * @return the current line number of the input string
     */
    public int getLineNumber() {
        return scannerInput.getLineNumber();
    }

    /**
     * @return the current recognized string
     */
    public String getString() {
        return recognizeString;
    }

    // End ScanService methods

    public int getLastTokenPos() {
        if (recognizeString.equals("") && !accumulatedTokens.isEmpty()) {
            accumulatedTokens.removeElementAt(accumulatedTokens.size()-1);
            scannerInput.getLastTokenPos(true); // true == remove Element
        }
        int lastTokenPos = scannerInput.getLastTokenPos(false).intValue();
        scannerInput.setIndex(lastTokenPos);
        setActive();
        recognizeString = "";
        bestAutomat = "";
        return lastTokenPos;
    }

    /**
     * @return the name of the scanner
     */
    public String getName() {
        return scannerName;
    }

    /**
     * @return the string representation of the passive automata.
     */
    public String getPassiveAutomata() {
        return getAutomata(passiveAutomata);
    }

    /**
     * @return the current index of the input
     * This is the index of the next reading character.
     */
    public int getInputIndex() {
        return scannerInput.getCurrentPosition();
    }

    /**
     * @return the current scanner input
     * @see ScannerInput
     */
    public ScannerInput getScannerInput() {
        return this.scannerInput;
    }

    /**
     * @return the complete accumulated token.
     */
    public Vector getToken() {
        return accumulatedTokens;
    }

    /**
     * @return the accumulated token as a vector without the intern
     * JACCIE Token this are JACCIE- Verbatims, Strings, Separators and Comments
     */
    public Vector getResult() {
        Vector result = new Vector();
        Enumeration tokenList = accumulatedTokens.elements();
        while (tokenList.hasMoreElements()) {
            String token = (String)tokenList.nextElement();
            if (!token.startsWith("JACCIE")) result.addElement(token);
        }
        return result;
    }

    /**
     * @return the accumulated token as a string without the intern
     * JACCIE Token this are JACCIE- Verbatims, Strings, Separators and Comments
     * The Form is <TokenName> space "<TokenContents>" newline
     */
    public String getResultAsString() {
        Vector result = this.getResult();
        StringBuffer resultBuffer = new StringBuffer();
        for (int i=0; i<result.size();i++)
             resultBuffer.append((String)result.elementAt(i)+ls);
        return resultBuffer.toString();
    }

    /**
     * @return the input index of a accumulated token.
     * @param index the number of the accumulated token.
     * If you select a token in the interactive testing area this
     * method will be called, and the scanner input indesx will be set back.
     */
    public int getTokenPosAt(int index) {
        int size = accumulatedTokens.size();
        for (int i=size-1; i>=index; i--) {
            accumulatedTokens.removeElementAt(i);
            scannerInput.getLastTokenPos(true);
        }
        setActive();
        recognizeString = "";
        bestAutomat = "";
        int position = scannerInput.getLastTokenPos(false).intValue();
        scannerInput.setIndex(position);
        return position;
    }

    /**
     * Resets the scanner to the init state. This method should be
     * called before you scan an imput.
     */
    public void resetScanner() {
        bestAutomat = "";
        accumulatedTokens.removeAllElements();
        recognizeString = "";
        firstSymbol = true;
        setActive();
        scannerInput.reset();
    }

    /**
     * Scans the complete input and throws a ScannerException if
     * a character not valid.
     * If the input is empty the scan stops. A not correct ending
     * scanner input is not an error. So you are able to scan
     * encomplete inputs.
     */
    public void scanAll() throws Exception{
        Character symbol = scannerInput.getChar();
        if (symbol == null)
            endOfInputHandling();
        else {
            int validChar = scan(symbol);
            if (validChar == -1) throw new Exception("Error while scanning"+
                          " [ "+symbol+ " ] "+"at line"+" "+errorLine+ " "+
                          "at column"+" "+errorColumn+""+ls+
                          "Input set back to last recognize token index");
            else scanAll();
        }
    }

    /**
     * Scans one character of the input and throws a ScannerException if
     * the character not valid.
     * If the input is empty the scan stops. A not correct ending
     * scanner input is not an error. So you are able to scan
     * encomplete inputs.
     */
    public void scanOneChar() throws Exception{
        Character symbol = scannerInput.getChar();
        if (symbol == null)
            endOfInputHandling();
        else {
            int validChar = scan(symbol);
            if (validChar == -1) throw new Exception("Error while scanning"+
                          " [ "+symbol+ " ] "+"at line"+" "+errorLine+ " "+
                          "at column"+" "+errorColumn+""+ls+
                          "Input set back to last recognize token index");
        }
    }

    /**
     * Scans one token and throws a ScannerException if
     * a character not valid.
     * If the input is empty the scan stops. A not correct ending
     * scanner input is not an error. So you are able to scan
     * encomplete inputs.
     */
    public void scanOneToken() throws Exception{
        Character symbol = scannerInput.getChar();
        if (symbol == null)
            endOfInputHandling();
        else {
            int validChar = scan(symbol);
            if (validChar == -1)
                throw new Exception("Error while scanning"+" [ "+symbol+
                          " ] "+"at line"+" "+errorLine+ " "+
                          "at column"+" "+errorColumn+""+ls+
                          "Input set back to last recognize token index");
            if (validChar != 1) scanOneToken();
        }
    }

    /**
     * @return a string represenatation of a subclass of scanner.
     * You are able to take this string and use it as a new scanner class.
     * It is used by the make event of the scanner definition.
     */
    public String toString() {
        String javaString = "";
        String string ="";
        if (javaCode != null) {
           javaString = javaCode.toString();
           int indexOfImport = javaString.indexOf("public class",0);
           string += javaString.substring(0,indexOfImport);
           javaString = javaString.substring(indexOfImport+7);
        }
        string += "public class "+getName()+" extends ScannerScanner {"+ls+ls;
        string += "     public "+getName()+"() {"+ls;
        string += "     super(\""+getName()+"\",new ScannerInput());"+ls+ls;
        if (javaCode != null) {
            int indexOfClass = javaString.indexOf("class",0);
            int indexOfImplements = javaString.indexOf("implements",0);
            String fileName = javaString.substring(indexOfClass+6,indexOfImplements-1);
            string += "     super.setStartEndAction(new ";
            string += fileName+"());"+ls;
        }
        string += "     ScannerInput input = super.getScannerInput();"+ls;
        string += scannerInput.toString()+ ""+ls;
        string += "     Automat automat;"+ls;
        string += "     Transition transition;"+ls;
        string += "     State state;"+ls+ls;
        for (int i=0; i<automataList.size(); i++) {
            string += ((Automat)automataList.elementAt(i)).toString()+""+ls;
            string += "          add(automat);"+ls+ls;
        }
        string += "     } // End of constructor"+ls+ls;
        string += "} // End of class "+getName()+ls+ls;
        if (javaCode != null) string+= javaString;

        if (string.indexOf("import Data.*;") != -1) return string;
        else return "import Data.*;"+ls+ls+string;
    }
} // Scanner